#include "transform.h"

#include <gtest/gtest.h>

namespace {

using mathengine::Matrix44f;
using mathengine::Vec2f;
using mathengine::Vec3f;
using mathengine::Vec4f;
using raster::Camera;
using raster::CoordAToCoordB;
using raster::Modeling;
using raster::Orthographic;
using raster::Perspective;
using raster::Rotate;
using raster::RotateX;
using raster::RotateY;
using raster::RotateZ;
using raster::Scale;
using raster::ToNormalTransform;
using raster::Transform;
using raster::Translate;
using raster::Viewport;

class TransformTest : public testing::Test {
protected:
    void SetUp() override {
        tb = Transform(Matrix44f{ 1,  2,  3,  4,
                                  5,  6,  7,  8,
                                  9, 10, 11, 12,
                                 13, 14, 15, 16 });

        tc = Transform(Matrix44f::random(-100, 100));
        while (tc.invalid() || tc == ta)
        {
            tc = Transform(Matrix44f::random(-100, 100));
            ;
        }
    }

    void TearDown() override {
    }

    Transform ta; // identity matrix
    Transform tb; // invalid
    Transform tc; // valid, not identity matrix
};

TEST_F(TransformTest, Constructor) {
    Transform t1(1, 2, 3, 4,
                    5, 6, 7, 8,
                    9, 10, 11, 12,
                    13, 14, 15, 16);

    float a[] = {1, 2, 3, 4,
                    5, 6, 7, 8,
                    9, 10, 11, 12,
                    13, 14, 15, 16};
    float b[4][4] = {{1, 2, 3, 4},
                        {5, 6, 7, 8},
                        {9, 10, 11, 12},
                        {13, 14, 15, 16}};
    Transform t2(a);
    Transform t3(b);
    Transform t4(Matrix44f{1, 2, 3, 4,
                            5, 6, 7, 8,
                            9, 10, 11, 12,
                            13, 14, 15, 16});

    Transform t5(t1);
    Transform t6 = t1;

    EXPECT_EQ(ta, Transform(Matrix44f::identity()));
    EXPECT_FALSE(ta == tb);
    // EXPECT_NE(ta, tb);
    EXPECT_EQ(tb, t1);
    EXPECT_EQ(tb, t2);
    EXPECT_EQ(tb, t3);
    EXPECT_EQ(tb, t4);
    EXPECT_EQ(tb, t5);
}

TEST_F(TransformTest, NumMultiply) {
    EXPECT_EQ(ta * 1, ta);
    EXPECT_EQ(1 * ta, ta);
    EXPECT_EQ(ta * 0, Transform(Matrix44f::zero()));

    EXPECT_EQ(tb * 2, Transform(Matrix44f(
                            {2, 4, 6, 8,
                            10, 12, 14, 16,
                            18, 20, 22, 24,
                            26, 28, 30, 32})));
    EXPECT_EQ(tb * 2, 2 * tb);
}

TEST_F(TransformTest, CompositeTransform) {
    EXPECT_EQ(tc * ta, tc);

    Transform T(1, 0, 0, 2,
                0, 1, 0, 3,
                0, 0, 1, 1,
                0, 0, 0, 1);
    Transform S(0.5,   0,   0, 0,
                  0, 0.5,   0, 0,
                  0,   0, 0.5, 0,
                  0,   0,   0, 1);
    Transform Ry(0.707, 0, 0.707, 0,
                     0, 1,     0, 0,
                -0.707, 0, 0.707, 0,
                     0, 0,     0, 1); // theta = 45°
    Transform M(0.3535,   0, 0.3535, 2,
                     0, 0.5,      0, 3,
               -0.3535,   0, 0.3535, 1,
                     0,   0,      0, 1);

    EXPECT_EQ(T * S * Ry, M);
}

TEST_F(TransformTest, Inverse) {
    auto a = ta;
    auto c = tc;

    EXPECT_EQ(a.inverse(), ta);
    EXPECT_NE(c.inverse(), tc);
    EXPECT_EQ(a * a.inverse(), Transform(Matrix44f::identity()));
    EXPECT_EQ(c * c.inverse(), Transform(Matrix44f::identity()));

    a.invert();
    c.invert();
    EXPECT_EQ(a, ta);
    EXPECT_NE(c, tc);
    EXPECT_EQ(ta * a, Transform(Matrix44f::identity()));
    EXPECT_EQ(tc * c, Transform(Matrix44f::identity()));
}

TEST_F(TransformTest, TransformVector)
{
    Vec4f v1{2, 3, 4, 1};
    EXPECT_EQ(ta(v1), v1);

    auto A = Translate(1, 0, -1);
    Vec4f v2{3, 3, 3, 1};
    EXPECT_EQ(A(v1), v2);

    auto B = RotateZ(90);
    Vec4f v3{1, 0, 0, 0};
    Vec4f v4{0, 1, 0, 0};
    EXPECT_EQ(B(v3), v4);
}

TEST_F(TransformTest, ToNormal) {
    Transform vT = Transform(Matrix44f::identity());
    Transform nT = vT.toNormal();
    EXPECT_EQ(nT, Transform(Matrix44f::identity()));

    vT = Transform( 1, 2, 0, 1,
                    0, 1, 1, 0,
                    2, 0, 1, 1,
                    1, 1, 0, 1);
    EXPECT_FALSE(vT.invalid());
    nT = vT.toNormal();
    EXPECT_EQ(nT, Transform( 2,  1, -1, -3,
                            -1,  0,  1,  1,
                             1,  0,  0, -1,
                            -3, -1,  1,  5 ));
}

class ViewingTransformationTest : public testing::Test {
protected:
    void SetUp() override {
        T = Transform( 1, 0, 0, 2,
                       0, 1, 0, 3,
                       0, 0, 1, 1,
                       0, 0, 0, 1 );
        S = Transform(0.5,   0,   0, 0,
                        0, 0.5,   0, 0,
                        0,   0, 0.5, 0,
                        0,   0,   0, 1);
        Rx = Transform( 1,      0,       0, 0,
                        0, 0.7071, -0.7071, 0,
                        0, 0.7071,  0.7071, 0,
                        0,      0,       0, 1); // 45°
        Ry = Transform(  0.7071, 0, 0.7071, 0,
                              0, 1,      0, 0,
                        -0.7071, 0, 0.7071, 0,
                              0, 0,      0, 1); // 45°
        Rz = Transform( 0.7071, -0.7071, 0, 0,
                        0.7071,  0.7071, 0, 0,
                             0,       0, 1, 0,
                             0,       0, 0, 1); // 45°
    }

    void TearDown() override {
    }

    Transform T;
    Transform S;
    Transform Rx;
    Transform Ry;
    Transform Rz;
};

TEST_F(ViewingTransformationTest, TranslateTransform) {
    EXPECT_EQ(Translate(2, 3, 1), T);
    EXPECT_EQ(Translate(Vec3f({2, 3, 1})), T);
}

TEST_F(ViewingTransformationTest, ScaleTransform) {
    EXPECT_EQ(Scale(0.5, 0.5, 0.5), S);
    EXPECT_EQ(Scale(Vec3f({0.5, 0.5, 0.5})), S);
}

TEST_F(ViewingTransformationTest, RotateTransform) {
    EXPECT_EQ(RotateX(45), Rx);
    EXPECT_EQ(RotateY(45), Ry);
    EXPECT_EQ(RotateZ(45), Rz);

    EXPECT_EQ(Rotate(1, 0, 0, 45), Rx);
    EXPECT_EQ(Rotate(0, 1, 0, 45), Ry);
    EXPECT_EQ(Rotate(0, 0, 1, 45), Rz);

    EXPECT_EQ(Rotate(4, 0, 0, 45), Rx);
    EXPECT_EQ(Rotate(0, 5, 0, 45), Ry);
    EXPECT_EQ(Rotate(0, 0, 6, 45), Rz);

    EXPECT_EQ(Rotate(Vec3f{1, 0, 0}, 45), Rx);
    EXPECT_EQ(Rotate(Vec3f{0, 1, 0}, 45), Ry);
    EXPECT_EQ(Rotate(Vec3f{0, 0, 1}, 45), Rz);

    EXPECT_EQ(Rotate(Vec3f{4, 0, 0}, 45), Rx);
    EXPECT_EQ(Rotate(Vec3f{0, 5, 0}, 45), Ry);
    EXPECT_EQ(Rotate(Vec3f{0, 0, 6}, 45), Rz);
}

TEST_F(ViewingTransformationTest, ModelingTransform)
{
    auto A = Modeling(Vec3f{  2,   3,   1},
                      Vec3f{0.5, 0.5, 0.5},
                      Vec3f{  0,   0,   1}, 45);
    auto B = T * S * Rz;
    EXPECT_EQ(A, B);

    A = Modeling(  2,   3,   1,
                 0.5, 0.5, 0.5,
                   0,   0,   1, 45);
    EXPECT_EQ(A, B);
}

TEST_F(ViewingTransformationTest, CameraTransform) {
    auto A = Camera(Vec3f{0, 0, 5}, Vec3f{0, 0, -1}, Vec3f{0, 1, 0});
    auto B = Transform(1, 0, 0,  0,
                       0, 1, 0,  0,
                       0, 0, 1, -5,
                       0, 0, 0,  1);
    EXPECT_EQ(A, B);

    // unit vector is not necessary
    A = Camera(Vec3f{0, 0, 5}, Vec3f{0, 0, -10}, Vec3f{0, 20, 0});
    EXPECT_EQ(A, B);
}

TEST_F(ViewingTransformationTest, OrthographicTransform)
{
    auto A = Orthographic(-2, 2, -1.5, 1.5, -0.1, -100);
    auto B = Transform(0.5f,     0,       0,             0,
                          0, 2/3.f,       0,             0,
                          0,     0, 2/99.9f, -100.1f/99.9f,
                          0,     0,       0,             1);

    EXPECT_EQ(A, B);

    A = Orthographic(Vec3f{-2, -1.5, -0.1}, Vec3f{2, 1.5, -100});
    EXPECT_EQ(A, B);
}

TEST_F(ViewingTransformationTest, PerspectiveTransform) {
    auto A = Perspective(-1, 1, -0.5, 0.5, -0.1, -100);
    auto B = Transform(0.1,   0,       0,        0,
                         0, 0.2,       0,        0,
                         0,   0, -1.002f, -0.2002f,
                         0,   0,      -1,        0);
    EXPECT_EQ(A, B);

    float sx = 1 / (1.777 * 0.4142);
    float sy = 1 / 0.4142;
    A = Perspective(0.1, 100.0, 45, 1.777); // aspect=16:9 ≈ 17.77
    B = Transform(sx,  0,       0,        0,
                   0, sy,       0,        0,
                   0,  0, -1.002f, -0.2002f,
                   0,  0,      -1,        0);
    EXPECT_EQ(A, B);
}

TEST_F(ViewingTransformationTest, ViewportTransformation) {
    auto A = Viewport(800, 600, Vec2f{100, 200});
    auto B = Transform(400,    0, 0, 500,
                         0, -300, 0, 500,
                         0,    0, 1,   0,
                         0,    0, 0,   1);
    EXPECT_EQ(A, B);

    A = Viewport(800, 600);
    B = Transform(400,    0, 0, 400,
                    0, -300, 0, 300,
                    0,    0, 1,   0,
                    0,    0, 0,   1);
    EXPECT_EQ(A, B);
}

TEST_F(ViewingTransformationTest, CoordAToCoordBTransform) {
    auto A = CoordAToCoordB(Vec3f{1, 0, 0}, Vec3f{0, 1, 0}, Vec3f{0, 0, 1}, Vec3f{0, 0, 3});
    auto B = Transform(1, 0, 0,  0,
                       0, 1, 0,  0,
                       0, 0, 1, -3,
                       0, 0, 0,  1);
    EXPECT_EQ(A, B);
}

} // namespace
